/**********************************************************************

  Audacity: A Digital Audio Editor

  @file LV2Instance.cpp

  Paul Licameli split from LV2Effect.cpp

  Audacity(R) is copyright (c) 1999-2008 Audacity Team.
  License: GPL v2 or later.  See License.txt.

**********************************************************************/
#if defined(USE_LV2)

#if defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wparentheses"
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#elif defined(__clang__)
#pragma clang diagnostic ignored "-Wparentheses"
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif

#include "LV2Instance.h"
#include "LV2Wrapper.h"
#include "AudacityException.h"

LV2Instance::LV2Instance(
    const PerTrackEffect& effect, const LV2FeaturesList& features,
    const LV2Ports& ports)
    : PerTrackEffect::Instance{effect}
    , mFeatures{features}
    , mPorts{ports}
{
    LV2Preferences::GetUseLatency(effect, mUseLatency);

    int userBlockSize;
    LV2Preferences::GetBufferSize(effect, userBlockSize);
    mUserBlockSize = std::max(1, userBlockSize);
    lv2_atom_forge_init(&mForge, mFeatures.Base().URIDMapFeature());
}

LV2Instance::~LV2Instance() = default;

void LV2Instance::MakeMaster(const EffectSettings& settings,
                             double sampleRate)
{
    // Come here only when doing non-realtime application of the effect, in which
    // case, we don't care about capturing the output ports
    if (mMaster && sampleRate == mFeatures.mSampleRate) {
        // Already made but be sure to connect control ports to the right place
        mMaster->ConnectControlPorts(mPorts, GetSettings(settings), nullptr);
        return;
    }
    mMaster = MakeWrapper(settings, sampleRate, nullptr);
    SetBlockSize(mUserBlockSize);
}

std::unique_ptr<LV2Wrapper>
LV2Instance::MakeWrapper(const EffectSettings& settings,
                         double sampleRate, EffectOutputs* pOutputs)
{
    return LV2Wrapper::Create(mFeatures, mPorts, mPortStates,
                              GetSettings(settings), sampleRate, pOutputs);
}

size_t LV2Instance::SetBlockSize(size_t maxBlockSize)
{
    mFeatures.mBlockSize = std::max(mFeatures.mMinBlockSize,
                                    std::min({ maxBlockSize, mUserBlockSize, mFeatures.mMaxBlockSize }));
    if (mMaster) {
        mMaster->SendBlockSize();
    }
    for (auto& pSlave : mSlaves) {
        pSlave->SendBlockSize();
    }
    return GetBlockSize();
}

size_t LV2Instance::GetBlockSize() const
{
    return mFeatures.mBlockSize;
}

unsigned LV2Instance::GetAudioInCount() const
{
    return mPorts.mAudioIn;
}

unsigned LV2Instance::GetAudioOutCount() const
{
    return mPorts.mAudioOut;
}

auto LV2Instance::GetLatency(const EffectSettings&, double) const
-> SampleCount
{
    if (mMaster && mUseLatency && mPorts.mLatencyPort >= 0) {
        return mMaster->GetLatency();
    }
    return 0;
}

// Start of destructive processing path
bool LV2Instance::ProcessInitialize(EffectSettings& settings,
                                    double sampleRate, ChannelNames chanMap)
{
    MakeMaster(settings, sampleRate);
    if (!mMaster) {
        return false;
    }
    for (auto& state : mPortStates.mCVPortStates) {
        state.mBuffer.reinit(GetBlockSize(), state.mpPort->mIsInput);
    }
    mMaster->Activate();
    return true;
}

size_t LV2Instance::ProcessBlock(EffectSettings&,
                                 const float* const* inbuf, float* const* outbuf, size_t size)
{
    using namespace LV2Symbols;
    if (size > GetBlockSize()) {
        return 0;
    }
    assert(mMaster); // else ProcessInitialize() returned false, I'm not called
    const auto instance = &mMaster->GetInstance();

    int i = 0;
    int o = 0;
    for (auto& port : mPorts.mAudioPorts) {
        lilv_instance_connect_port(instance,
                                   port->mIndex,
                                   const_cast<float*>(port->mIsInput ? inbuf[i++] : outbuf[o++]));
    }

    for (auto& state : mPortStates.mAtomPortStates) {
        state->SendToInstance(mForge, mPositionFrame, mPositionSpeed);
    }

    lilv_instance_run(instance, size);

    // Main thread consumes responses
    mMaster->ConsumeResponses();

    for (auto& state : mPortStates.mAtomPortStates) {
        state->ResetForInstanceOutput();
    }

    return size;
}

bool LV2Instance::RealtimeInitialize(EffectSettings&, double, size_t)
{
    for (auto& state : mPortStates.mCVPortStates) {
        state.mBuffer.reinit(GetBlockSize(), state.mpPort->mIsInput);
    }
    return true;
}

bool LV2Instance::RealtimeFinalize(EffectSettings&) noexcept
{
    return GuardedCall<bool>([&]{
        mSlaves.clear();
        for (auto& state : mPortStates.mCVPortStates) {
            state.mBuffer.reset();
        }
        return true;
    });
}

bool LV2Instance::RealtimeAddProcessor(EffectSettings& settings,
                                       EffectOutputs* pOutputs, unsigned, float sampleRate)
{
    // Connect to outputs only if this is the first processor for the track.
    // (What's right when a mono effect is on a stereo channel?  Unclear, but
    // this definitely causes connection with the first channel.)
    auto pInstance = LV2Wrapper::Create(mFeatures,
                                        mPorts, mPortStates, GetSettings(settings), sampleRate,
                                        mSlaves.empty() ? static_cast<LV2EffectOutputs*>(pOutputs) : nullptr);
    if (!pInstance) {
        return false;
    }
    pInstance->Activate();
    mSlaves.push_back(move(pInstance));
    return true;
}

bool LV2Instance::RealtimeSuspend()
{
    if (mMaster) {
        mMaster->Deactivate();
    }
    for (auto& pSlave : mSlaves) {
        pSlave->Deactivate();
    }

    mPositionSpeed = 0.0;
    mPositionFrame = 0;
    mRolling = false;

    return true;
}

bool LV2Instance::RealtimeResume()
{
    if (mMaster) {
        mMaster->Activate();
    }
    for (auto& pSlave : mSlaves) {
        pSlave->Activate();
    }

    mPositionSpeed = 1.0;
    mPositionFrame = 0;
    mRolling = true;

    return true;
}

bool LV2Instance::RealtimeProcessStart(MessagePackage&)
{
    mNumSamples = 0;
    for (auto& state : mPortStates.mAtomPortStates) {
        state->SendToInstance(mForge, mPositionFrame, mPositionSpeed);
    }
    return true;
}

size_t LV2Instance::RealtimeProcess(size_t group, EffectSettings&,
                                    const float* const* inbuf, float* const* outbuf, size_t numSamples)
{
    if (group >= mSlaves.size()) {
        return 0;
    }
    assert(numSamples <= (size_t)GetBlockSize());

    if (group < 0 || group >= (int)mSlaves.size()) {
        return 0;
    }

    const auto slave = mSlaves[group].get();
    const auto instance = &slave->GetInstance();

    int i = 0;
    int o = 0;
    for (auto& port : mPorts.mAudioPorts) {
        lilv_instance_connect_port(instance,
                                   port->mIndex,
                                   const_cast<float*>(port->mIsInput ? inbuf[i++] : outbuf[o++]));
    }

    mNumSamples = std::max(numSamples, mNumSamples);

    if (mRolling) {
        lilv_instance_run(instance, numSamples);
    } else {
        while (--i >= 0) {
            for (decltype(numSamples) s = 0; s < numSamples; s++) {
                outbuf[i][s] = inbuf[i][s];
            }
        }
    }

    // Background thread consumes responses from yet another worker thread
    slave->ConsumeResponses();

    for (auto& state : mPortStates.mAtomPortStates) {
        state->ResetForInstanceOutput();
    }

    if (group == 0) {
        mPositionFrame += numSamples;
    }

    return numSamples;
}

bool LV2Instance::RealtimeProcessEnd(EffectSettings&) noexcept
{
    return GuardedCall<bool>([&]{
        // Nothing to do if we did process any samples
        if (mNumSamples == 0) {
            return true;
        }

        // Why is this not also done on the destructive processing path?
        // Because it is soon dimissing the modal dialog anyway.
        for (auto& state : mPortStates.mAtomPortStates) {
            state->ReceiveFromInstance();
        }

        mNumSamples = 0;

        return true;
    });
}

#endif
